#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Threading.Tasks;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Core.Common;
using BitwiseOre.SharedMemory.Core.IPCQueues;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.IPCQueues
{
    [TestFixture]
    public sealed class ListenerQueueFixture : AssertionHelper
    {
        [Test(Description = "Checks that dequeueing from the queue waits until the queue is not empty")]
        public void Dequeue_WaitsOnNotEmpty()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var queue = new ListenerQueue(notempty.Object, new Mock<IGuidQueue>().Object);

            queue.Dequeue(TimeSpan.FromMinutes(1));

            notempty.Verify(s => s.Wait(It.IsAny<TimeSpan>()),
                            "Dequeue did not wait on the not empty semaphore.");
        }

        [Test(Description = "Checks that dequeueing from the queue waits using the given timeout")]
        public void Dequeue_WaitsWithGivenTimeout()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var queue = new ListenerQueue(notempty.Object, new Mock<IGuidQueue>().Object);
            var timeout = TimeSpan.FromMinutes(1);

            queue.Dequeue(timeout);

            notempty.Verify(s => s.Wait(It.Is<TimeSpan>(t => t == timeout)),
                            "Dequeue did not properly use the supplied timeout.");
        }

        [Test(Description = "Checks that dequeueing from the listener queue dequeues from the underlying guid queue")]
        public void Dequeue_DequeuesFromGuidQueue()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var guidqueue = new Mock<IGuidQueue>();
            
            var queue = new ListenerQueue(notempty.Object, guidqueue.Object);
            queue.Dequeue(TimeSpan.FromMinutes(1)).Wait();

            guidqueue.Verify(q => q.Dequeue(),
                             "Dequeue did not dequeue from underlying guid queue.");
        }

        [Test(Description = "Checks that dequeueing from the queue with non-zero pending dequeues does not wait " +
                            "for non-empty queue")]
        public void Dequeue_WtihNonZeroPending()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var guidqueue = new Mock<IGuidQueue>();
            guidqueue.Setup(g => g.DecrementNonZeroPending()).Returns(true);
            
            var queue = new ListenerQueue(notempty.Object, guidqueue.Object);
            queue.Dequeue(TimeSpan.FromMinutes(1));
            
            notempty.Verify(s => s.Wait(It.IsAny<TimeSpan>()), Times.Never(),
                "Unexpected wait for a non-empty queue.");
        }


        [Test(Description = "Checks that dequeueing from the queue attempts to decrement the pending count")]
        public void Dequeue_AttemptsToDecrementPendingDequeues()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var guidqueue = new Mock<IGuidQueue>();
            var queue = new ListenerQueue(notempty.Object, guidqueue.Object);

            queue.Dequeue(TimeSpan.FromMinutes(1));

            guidqueue.Verify(g => g.DecrementNonZeroPending(),
                             "Dequeue did not attempt to decrement the pending count.");
        }


        [Test(Description = "Checks that enqueueing to the queue signals that the queue is not empty")]
        public void Enqueue_SignalsNotEmpty()
        {
            var notempty = new Mock<ISemaphore>();
            var queue = new ListenerQueue(notempty.Object, new Mock<IGuidQueue>().Object);

            queue.Enqueue(Guid.NewGuid());

            notempty.Verify(s => s.Release(),
                            "Enqueue did not signal that the queue is not empty.");
        }

        [Test(Description = "Checks that enqueueing to the listner queue enqueues to the underlying guid queue")]
        public void Enqueue_EnqueuesToGuidQueue()
        {
            var guidqueue = new Mock<IGuidQueue>();
            var queue = new ListenerQueue(new Mock<ISemaphore>().Object, guidqueue.Object);
            var guid = Guid.NewGuid();

            queue.Enqueue(guid);

            guidqueue.Verify(q => q.Enqueue(It.Is<Guid>(g => g == guid)),
                             "Enqueue did not enqueue the guid to the underlying guid queue.");
        }

        [Test(Description = "Checkes that waiting for the queue to be not empty actually does so")]
        public void WaitNotEmpty_WaitsOnNotEmpty()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var queue = new ListenerQueue(notempty.Object, new Mock<IGuidQueue>().Object);

            queue.WaitNotEmpty(TimeSpan.FromMinutes(1));

            notempty.Verify(s => s.Wait(It.IsAny<TimeSpan>()),
                            "WaitNotEmpty did not wait on the not empty semaphore.");
        }

        [Test(Description = "Checks that waiting for non-empty queue waits using the given timeout.")]
        public void WaitNotEmpty_WaitsWithGivenTimeout()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var queue = new ListenerQueue(notempty.Object, new Mock<IGuidQueue>().Object);
            var timeout = TimeSpan.FromMinutes(1);

            queue.WaitNotEmpty(timeout);

            notempty.Verify(s => s.Wait(It.Is<TimeSpan>(t => t == timeout)),
                            "Dequeue did not properly use the supplied timeout.");
        }

        [Test(Description = "Checks that waiting for a non-empty queue increments the pending count")]
        public void WaitNotEmpty_IncrementsPendingCount()
        {
            var notempty = new Mock<ISemaphore>();
            notempty.Setup(s => s.Wait(It.IsAny<TimeSpan>())).Returns(Task.Factory.StartNew(() => { }));
            var guidqueue = new Mock<IGuidQueue>();
            var queue = new ListenerQueue(notempty.Object, guidqueue.Object);

            queue.WaitNotEmpty(TimeSpan.FromMinutes(1)).Wait();

            guidqueue.Verify(g => g.IncrementPending(),"Pending dequeue count was not incremented.");
        }
    }
}